/*
网关是集群的 “入口节点”
此文件定义了网关（Gate）组件的核心结构体和生命周期方法。
Gate是分布式游戏服务器架构中的入口组件，主要负责管理客户端连接、接收消息并进行路由分发。
它实现了 Component接口（Init, Start, Stop），意味着它可以被主容器统一管理。目前这是一个基础框架，
后续将在此添加网络服务器、定位器、注册中心等具体实现。

2025/10/04
引入tcp服务器，实现简单收发和心跳机制
2025/10/05
引入注册中心，把网关服务注册进去
2025/10/07
创建网关消息处理器handler.go
2025/10/09
引入rpc服务，更新网关组件以支持RPC调用
2025/10/09
增加加点分发服务
通过负载均衡将请求分给合适的节点服务端，实现压力分摊和业务路由
是连接 “玩家客户端” 和 “节点服务端” 的核心枢纽（消息处理那块）
2025/10/12
增加负载均衡功能到网关组件中
2025/10/14
增加健康检查器
2025/10/17
使用配置中心
2025/10/20
增加事件总线
*/
package gate

import (
	"context" // 提供上下文（用于RPC调用的超时、取消等控制）
	"fmt"     // 格式化字符串（如拼接URL、错误信息）
	"gameServer/config"
	"gameServer/eventbus"
	"gameServer/health"
	"gameServer/locate"          // 封装负载均衡
	"gameServer/locate/redis"    // redis服务信息定位器客户端
	"gameServer/log"             // 导入日志模块           // 日志打印
	"gameServer/network/tcp"     // 导入TCP服务器模块（处理网络连接）
	"gameServer/registry"        // 导入注册中心接口定义
	"gameServer/registry/memory" // 导入内存注册中心实现
	"gameServer/rpc"             // 导入RPC模块（实现跨节点远程调用）
	"math/rand"                  // 生成随机数（用于服务ID）
	"net"                        // 网络工具（解析地址）
	"strconv"                    // 字符串转换（端口字符串转int）
	"time"                       // 时间工具（随机数种子）
)

/* //* 网关组件核心实现，作为集群入口节点，负责管理客户端连接、消息路由分发、RPC调用转发、负载均衡和健康检查等核心功能 */

// Gate 网关组件
type Gate struct {
	tcpServer     *tcp.Server                // 集成了 TCP 服务器实例
	registry      registry.Registry          //集成内存注册中心
	serviceID     string                     // 服务ID，用于注册中心
	handler       *GateHandler               // 网关消息处理器
	rpcClient     *rpc.HttpClient            // 预留：网关自身的RPC客户端（目前未使用）
	nodeCache     map[string]*rpc.HttpClient // 节点ID到RPC客户端的缓存（避免重复创建连接，提升效率）
	balancer      locate.Balancer            //负载均衡接口
	locator       registry.Locator           // 定位器接口
	healthChecker *health.HealthChecker      // 健康检查器
	eventBus      eventbus.EventBus          // 事件总线
}

// NewGate 创还能一个网关组件
func NewGateWithConfig(gateConfig *config.GateConfig) *Gate {

	// 初始化日志系统
	if err := log.InitFromConfig(gateConfig.ToLogConfig()); err != nil {
		log.Fatalf("Failed to init log system: %v", err)
		// 继续创建网关
	}

	// 设置随机种子，生成随机服务ID(不妨考虑是否会重复)
	rand.Seed(time.Now().UnixNano())
	// 生成服务ID：前缀"gate-" + 随机整数（转为字符串）
	serviceID := "gate-" + strconv.FormatInt(rand.Int63(), 10)

	// // 创建事件总线（可以使用全局事件总线）
	// eventBus := eventbus.GlobalEventBus()
	// 根据配置创建事件总线
	eventBusConfig := &eventbus.Config{
		Type:       gateConfig.EventBus.Type,
		BufferSize: gateConfig.EventBus.BufferSize,
	}
	eventBus, err := eventbus.CreateEventBus(eventBusConfig)
	if err != nil {
		log.Errorf("Failed to create event bus: %v", err)
		// 使用内存事件总线作为后备
		eventBus = eventbus.NewMemoryEventBus()
	}
	gate := &Gate{
		// hosts解析域名优先于DNS
		// tcpServer: tcp.NewServer(":52051"),    // 初始化TCP服务器，默认监听":52051"（所有网卡的52051端口）
		registry:  memory.NewMemoryRegistry(), // 初始化内存注册中心 多态 接口 = 指针
		serviceID: serviceID,                  // 服务ID，用于注册中心 每个服务唯一ID
		nodeCache: make(map[string]*rpc.HttpClient),
		eventBus:  eventBus,
	}
	// 根据配置创建tcp服务器
	addr := fmt.Sprintf("%s:%d", gateConfig.Server.Addr, gateConfig.Server.Port)
	log.Infof("addr from config.json: %s", addr)
	gate.tcpServer = tcp.NewServer(addr, gate.handler)

	// 根据配置创建健康检查器
	checkInterval := time.Duration(gateConfig.Health.CheckInterval) * time.Second
	gate.healthChecker = health.NewHealthChecker(gate.registry, checkInterval)

	// 根据配置创建健康检查器，默认32s
	// gate.healthChecker = health.NewHealthChecker(gate.registry, 30*time.Second)

	// 根据配置创建负载均衡器
	switch gateConfig.LoadBalance.Type {
	case "random":
		gate.balancer = locate.NewRandomBalancer()
	case "route":
		gate.balancer = locate.NewRouteBaseBalancer()
	default:
		gate.balancer = locate.NewRoundRobinBalancer()

	}

	// 创建处理器
	gate.handler = NewGateHandler(gate)
	// 使用默认处理器 创建网络服务Server
	// gate.tcpServer = tcp.NewServer(":52051", gate.handler)
	return gate

	// 在适当的地方发布事件，例如用户连接、断开连接等
}

// 保持原有NewGate函数作为兼容
func NewGate() *Gate {
	// 使用默认配置
	gateConfig := config.NewGateConfig()
	return NewGateWithConfig(gateConfig)
}

// 设置Tcp服务器配置(ip:port) 这里还有消息处理器，其他地方设置了
func (g *Gate) SetTcpServer(addr string) *Gate {
	g.tcpServer = tcp.NewServer(addr, g.handler)
	return g // 返回自身指针，支持链式调用
}

// 设置注册中心 （目前是内存（本地）注册中心，可以切换其他的）
func (g *Gate) SetRegistry(registry registry.Registry) *Gate {
	// 让消息处理器与网关共用同一个注册中心（避免不一致）
	g.registry = registry
	g.handler.gate.registry = registry // 和消息处理器共用一个注册中心说是
	return g
}

// 设置负载均衡器
func (g *Gate) SetBalancer(balancer locate.Balancer) *Gate {
	g.balancer = balancer
	// 负载均衡器也要用统一的，和消息处理器统一，这是gate的消息处理器
	g.handler.SetBalancer(balancer)
	return g
}

// 设置服务定位器
func (g *Gate) SetLocator(locator registry.Locator) *Gate {
	g.locator = locator
	// 设置注册中心的定位器
	if err := g.registry.SetLocator(locator); err != nil {
		log.Errorf("Failed to set balancer for registry: %v", err)
	}
	return g
}

// Init 初始化网关组件
func (g *Gate) Init() error {
	log.Info("Gate component is initializing...")
	// 初始化逻辑写在这里
	// tcp 初始化逻辑（如果需要）
	return nil
}

// 启动网关组件
func (g *Gate) Start() error {
	log.Info("Gate component is starting...")
	// 启动逻辑写在这里
	// 记录事件总线配置
	log.Infof("Event bus type: %s", g.eventBus.Type())
	log.Infof("Event bus buffer size: %d", g.eventBus.BufferSize())
	// 启动健康检查器
	if err := g.healthChecker.Start(); err != nil {
		log.Errorf("Failed to start health checker: %v", err)
	}
	// 启动定位器（redis定位器）(类型断言操作，string.()进行类型转换)
	if redisLocator, ok := g.locator.(*redis.RedisLocator); ok {
		log.Infof("Starting Redis balancer with address: %s", redisLocator.GetAddr())
		if err := redisLocator.Start(); err != nil {
			log.Errorf("Failed to start Redis balancer: %v", err)
			// 继续启动，即使redis链接失败
		}
	}

	// 注册服务到注册中心

	// 获取ip和port
	host, portPtr, err := parseAddress(g.tcpServer.Addr())
	if err != nil {
		return err
	}
	// 语法问题，如果左边有一个新变量就可以用:=
	// 字符串转int，注意：portPtr是字符串类型，需要转换为int类型
	port, err := strconv.Atoi(portPtr)
	if err != nil {
		return err
	}
	// 构造服务注册信息
	serviceInfo := &registry.ServiceInfo{
		ID:      g.serviceID, // 唯一ID
		Name:    "gate",
		Address: host,
		Port:    port,
		Metadata: map[string]string{
			"protocol": "tcp",
			"status":   "active",
		},
	}
	// 将网关服务注册到注册中心
	if err := g.registry.Register(serviceInfo); err != nil {
		return err
	}

	// 启动Tcp服务器
	if err := g.tcpServer.Start(); err != nil {
		// 如果启动失败，注销服务
		g.registry.Deregister(g.serviceID)
		return err
	}
	log.Infof("TCP server started on %s", g.tcpServer.Addr())
	log.Infof("Service registered with ID: %s", g.serviceID)
	return nil
}

// 停止网关组件
func (g *Gate) Stop() error {
	log.Info("Gate component is stopping...")
	// 停止逻辑

	// 停止健康检查器
	if err := g.healthChecker.Stop(); err != nil {
		log.Errorf("Failed to stop health checker: %v", err)
	}
	// 停止Tcp服务器
	if err := g.tcpServer.Stop(); err != nil {
		return err
	}
	// 重注册中心注销服务
	if err := g.registry.Deregister(g.serviceID); err != nil {
		return err
	}

	// // 关闭所有rpc客户端连接
	// for _, client := range g.nodeCache {// 遍历缓存中的所有RPC客户端
	// 	// 这里可以添加关闭逻辑，目前http客户端（rpc.HttpClient）不需要显示关闭（底层由Go自动管理连接池）
	// }
	g.nodeCache = make(map[string]*rpc.HttpClient)

	log.Infof("Service deregistered with ID: %s", g.serviceID)
	log.Infof("TCP server stopped on %s", g.tcpServer.Addr())

	return nil
}

// 添加方法用于注册节点健康检查
// 从原节点的元数据中提取健康检查端口，构建专属的 “健康检查服务信息”，并将其注册到网关的健康检查器中，实现对该节点的 HTTP 健康监控
func (g *Gate) registerNodeHealthCheck(service *registry.ServiceInfo) error {
	// 获取健康检查端口
	// 原节点启动时（如 Node.Start() 方法），会将 “健康检查端口” 以键 healthPort、值为字符串的形式存入 Metadata（例如 {"healthPort": "28000"}）。
	healthPortStr, exists := service.Metadata["healthPort"]
	if !exists {
		return fmt.Errorf("node %s has no healthPort metadata", service.ID)
	}
	healthPort, err := strconv.Atoi(healthPortStr)
	if err != nil {
		return fmt.Errorf("invalid healthPort for node %s: %v", service.ID, err)
	}
	// 将 “原节点的健康检查属性” 封装为一个独立的 ServiceInfo 结构体（healthService），使健康检查器能像管理普通服务一样管理节点的健康监控，统一接入健康检查流程。
	// 创建健康检查服务信息（为原节点生成专属的健康检查服务描述）
	healthService := &registry.ServiceInfo{
		ID:      service.ID + "-health", // 健康服务唯一ID：原节点ID + "-health"
		Name:    "node-health",          // 健康服务名称：统一标识为"node-health"
		Address: service.Address,        // 健康服务地址：复用原节点的IP地址
		Port:    healthPort,             // 健康服务端口：转换后的整数端口
		Metadata: map[string]string{ // 健康服务元数据：关联原节点信息
			"parent": service.ID,               // "parent"：关联原节点的ID，便于追溯
			"type":   service.Metadata["type"], // "type"：继承原节点类型（如"game"）
		},
	}
	// 注册http健康检查：将健康服务信息和默认HTTP检查函数传入健康检查器
	return g.healthChecker.RegisterService(healthService, health.DefaultHttpCheck)
}

// 获得节点rpc客户端（缓存优先，未命中则从注册中心查询创建）
func (g *Gate) getNodeClient(nodeID string) (*rpc.HttpClient, error) {
	// 检查已有的缓存(map返回值和是否有，是true否false)
	if client, exists := g.nodeCache[nodeID]; exists {
		return client, nil
	}

	// 缓存中没有，是新的rpc节点，检查注册中心有没有这个，没有加加入并返回

	// 从注册中心获取节点信息
	services, err := g.registry.GetService("node")
	if err != nil {
		return nil, err
	}
	// 查找指定节点
	for _, service := range services {
		if service.ID == nodeID {
			// 获取rpc 端口 这个是node包中的rpc，应该去node中寻找（这个有）
			rpcPortStr, exists := service.Metadata["rpcPort"]
			if !exists {
				// 元数据中没有rpcPort，返回错误
				return nil, fmt.Errorf("node %s has no rpcPort metadata", nodeID)
			}
			// 将rpcPort字符串转为整数
			rpcPort, err := strconv.Atoi(rpcPortStr)
			if err != nil {
				// 端口格式无效，返回错误
				return nil, fmt.Errorf("invalid rpcPort for node %s: %v", nodeID, err)
			}
			// 创建rpc客户端
			baseURL := fmt.Sprintf("http://%s:%d", service.Address, rpcPort) // 拼接HTTP基础URL
			client := rpc.NewHttpClient(baseURL)                             // 创建HTTP RPC客户端
			// 缓存客户端
			g.nodeCache[nodeID] = client
			return client, nil
		}
	}
	return nil, fmt.Errorf("node %s not found", nodeID)
}

// 转发消息到节点
func (g *Gate) forwardToNode(nodeID string, route uint32, data []byte) ([]byte, error) {
	client, err := g.getNodeClient(nodeID)
	if err != nil {
		return nil, err
	}
	// 调用节点RPC 通过context进行信息共享（节点信息）
	return client.Call(context.Background(), route, data)
	// context.Background()返回默认空上下文（无超时、取消）；
}

// 解析地址字符串为主机和端口
func parseAddress(addr string) (host string, port string, err error) {
	host, port, err = net.SplitHostPort(addr)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse address %s: %v", addr, err)
	}
	if host == "" {
		// 此处在测试的时候可以写本机ip，实际使用的时候，要写对应服务的机器所在ip
		// 提供其他服务访问
		host = "localhost" //"rune_work.com"
	}

	return host, port, nil
}
